import { Event } from "@/utils/Event"
import { uuid } from "@/utils/uuid"

export function deepClone(target:any){
    let obj:any;
    if(target instanceof Array){
        obj = [];
        target.forEach(v=>{
            obj.push(deepClone(v));
        })
    }
    else if(typeof target == "object"){
        obj = {};
        Object.keys(target).forEach(k=>{
            obj[k] = target[k];
        })
    }
    else{
        obj = target;
    }

    return obj;
}

export class CustomElement {
    [k:string]:any
    $el: (HTMLElement | SVGElement ) & {[k:string]: any}
    tagName = "div"
    protected _style:{[k:string]:any} = {}
    Class:any
    ClassName:any
    event:Event
    parent:CustomElement|undefined
    uuid = uuid()
    userData:{[k:string]: any} = {}

    static isObject(element:any){
        return element instanceof this;
    }

    static from(element:any){
        const elementTem:any = new this(); 
        Object.keys(element).forEach(k=>{
             if(typeof elementTem[k] != "undefined" ){
                element[k] = deepClone(element[k]);
             }
        })
    }

    constructor(){
        this.event = new Event(this);
        this.$el = document.createElement(this.tagName);
        this.$el.that = this;

        this.ClassName = this.constructor.name;
        this.Class = this.constructor;
    }

    setTag(tag=this.tagName){
        this.$el = document.createElement(tag);
        this.tagName = tag;
        this.$el.that = this;
        this.updateStyle();
        return this;
    }

    setAttribute(attrName:string,value:any){
        this.$el.setAttribute(attrName,value);
        return this;
    }

    getAttribute(attrName:string){
        this.$el.getAttribute(attrName);
    }

    setStyle(style:{[k:string]:any}){
        Object.assign(this._style, style)
        this.updateStyle();
        return this;
    }

    clearStyle(){
        this._style = {};
        this.updateStyle();
        return this;
    }

    updateStyle(){
        const style = this.$el.style as any
        Object.keys(this._style).forEach(key=>{
            if( style[key] !== this._style[key]){
                style[key] = this._style[key]
            }
        })
        
        return this
    }

    toStyleString(){
        let style = ""
        Object.keys(this._style).forEach(key=>{
            style += `${key}:${this._style[key]};`
        })
        return style
    }

    setText(text: unknown){
        this.$el.innerText = text
        return this
    }

    getText(){
        return this.$el.innerText
    }

    setHtml(html: unknown){
        this.$el.innerHTML = html as string
        return this
    }

    getHtml(){
        return this.$el.innerHTML
    }

    padding(v: string){
        this.setStyle({
            padding: v
        })
        return this
    }

    margin(v: string){
        this.setStyle({
            margin: v
        })
        return this
    }

    marginTop(v: string){
        this.setStyle({
            marginTop: v
        })
        return this
    }

    marginBottom(v: string){
        this.setStyle({
            marginBottom: v
        })
        return this
    }

    marginLeft(v: string){
        this.setStyle({
            marginLeft: v
        })
        return this
    }

    marginRight(v: string){
        this.setStyle({
            marginRight: v
        })
        return this
    }


    position(mode: 'relative' | 'absolute' | 'fixed' , x?: unknown, y?: unknown, ){
        const obj:any = {
            position: mode,
        }

        x && (obj.left = x)
        y && (obj.top = y)
        this.setStyle(obj)
        return this
    }

    left(value:unknown){
        this.setStyle({
            left: value
        })
        return this
    }

    right(value:unknown){
        this.setStyle({
            right: value
        })
        return this
    }

    top(value:unknown){
        this.setStyle({
            top: value
        })
        return this
    }

    bottom(value:unknown){
        this.setStyle({
            bottom: value
        })
        return this
    }

    whiteSpace(value:'normal'|'nowrap'|'pre'|'pre-wrap'|'pre-line'){
        this.setStyle({
            whiteSpace: value
        })
        return this
    }

    textAlign(value:'center'|'left'|'right' & string){
        this.setStyle({
            textAlign: value
        })
        return this
    }

    transform2D(){
        const that = this
        const options = {
            translate:{
                x: '0',
                y: '0'
            },
            rotate:{
                z: '0',
            },
            scale:{
                x: 1,
                y: 1
            }
        }
        const controls = {

            translate(x: string, y: string){
                options.translate = {
                    x,y
                }
                return controls
            },

            rotate(z:any){
                options.rotate = {
                    z
                }
                return controls
            },

            scale(x: any, y: any){
                options.scale = {
                    x,y
                }
                return controls
            },

            create(){
                const {translate,rotate, scale } = options
                that.transform(`translate(${translate.x},${translate.y}) rotate(${rotate.z}deg) scale(${scale.x},${scale.y})`)
                return that
            }
        }

        return controls
    }
    
    border(borderValue: string){
        this.setStyle({
            border: borderValue
        })
        return this
    }

    borderRadius(borderRadiusValue: string){
        this.setStyle({
            borderRadius: borderRadiusValue
        })
        return this
    }

    outline(value: string){
        this.setStyle({
            outline: value
        })
        return this
    }

    color(color: string){
        this.setStyle({
            color: color
        })
        return this
    }

    background(background: string){
        this.setStyle({
            background: background
        })
        return this
    }

    width(width: string){
        this.setStyle({
            width: width
        })
        return this
    }

    height(height: string){
        this.setStyle({
            height: height
        })
        return this
    }

    minWidth(minWidth: string){
        this.setStyle({
            minWidth: minWidth
        })
        return this
    }

    maxWidth(maxWidth: string){
        this.setStyle({
            maxWidth: maxWidth
        })
        return this
    }

    minHeight(minHeight: string){
        this.setStyle({
            minHeight: minHeight
        })
        return this
    }

    maxHeight(maxHeight: string){
        this.setStyle({
            maxHeight: maxHeight
        })
        return this
    }

    overflow(overflow: 'visible' | 'hidden' | 'scroll' | 'auto' | 'inherit'){
        this.setStyle({
            overflow: overflow
        })
        return this
    }

    overflowX(overflow: 'visible' | 'hidden' | 'scroll' | 'auto' | 'inherit'){
        this.setStyle({
            overflowX: overflow
        })
        return this
    }

    overflowY(overflow: 'visible' | 'hidden' | 'scroll' | 'auto' | 'inherit'){
        this.setStyle({
            overflowY: overflow
        })
        return this
    }

    boxsizing(boxsizing: "border-box" = "border-box") {
        this.setStyle({
            boxSizing: boxsizing
        })
        return this
    }

    flex(flex: number){
        this.setStyle({
            flex: flex
        })
        return this
    }

    displyFlex(){
        this.setStyle({
            display: 'flex'
        })
        return this
    }

    displyBlock(){
        this.setStyle({
            display: 'block'
        })
        return this
    }

    displyNone(){
        this.setStyle({
            display: 'none'
        })
        return this
    }

    displyAuto(show:"flex"|"block"="block"){
        if(this._style.display === show) this.displyNone()
        else {
            show === "flex" ? this.displyFlex() : this.displyBlock()
        }
    
        return this
    }

    pointerEvents(pointerEvents: 'all' | 'none' | 'auto' | 'visiblePainted' | 'visibleFill' | 'visibleStroke' | 'visible' | 'painted' | 'fill' | 'stroke'){
        this.setStyle({
            pointerEvents,
        })
        return this
    }

    private nextCursor?: string
    cursor(
        cursor?:  | "alias"
        | "all-scroll"
        | "auto"
        | "cell"
        | "col-resize"
        | "context-menu"
        | "copy"
        | "crosshair"
        | "default"
        | "e-resize"
        | "ew-resize"
        | "grab"
        | "grabbing"
        | "help"
        | "move"
        | "n-resize"
        | "ne-resize"
        | "nesw-resize"
        | "no-drop"
        | "none"
        | "not-allowed"
        | "ns-resize"
        | "nw-resize"
        | "nwse-resize"
        | "pointer"
        | "progress"
        | "row-resize"
        | "s-resize"
        | "se-resize"
        | "sw-resize"
        | "text"
        | "vertical-text"
        | "w-resize"
        | "wait"
        | "zoom-in"
        | "zoom-out"){
        if(cursor){
            this.nextCursor = this.$el.style.cursor
            this.setStyle({
                cursor: cursor || this.nextCursor 
            })
        }
        else if( this.nextCursor){
            this.setStyle({
                cursor: this.nextCursor 
            })

            this.nextCursor = undefined
        }
        return this
    }

    transition(value: string){
        this.setStyle({
            transition: value
        })
        return this
    }

    transform(value: string){
        
        this.setStyle({
            transform: value
        })
        return this
    }

    zIndex(value: string | number){
        this.setStyle({
            zIndex: value
        })
        return this
    }

    fontSize(fontSize: string){
        this.setStyle({
            fontSize: fontSize
        })
        return this
    }

    getRect(){
        return this.$el.getBoundingClientRect()
    }

    appendChild(el:CustomElement,...arg:any[]){
        this.$el.appendChild(el.$el);
        this.event.emit("appendChild",el);
        el.parent = this;
        return this;
    }
    
    appendTo(el:any){
        el.appendChild(this.$el);
        this.event.emit("appendTo",el);
        return this;
    }

    removeChild(el:CustomElement){
        try{
            this.$el.removeChild(el.$el);
            this.event.emit("removeChild",el);
            el.parent = undefined;
        }
        catch(err){
            return this;
        }
        return this;
    }

    removeTo(){
        if(this.$el.parentElement){
            this.$el.parentElement.removeChild(this.$el);
            this.event.emit("remove",{});
        }
        return this;
    }

    on(eventName:string,calback: (e: { [key: string]: any; }) => any){
        this.event.on(eventName,calback);
        return this;
    }

    selfAuto(fun:(el:CustomElement)=>void){
        fun.call(this,this);

        return this;
    }

    
    get addEventListener(): (type: keyof DocumentEventMap, listener: (this: Document, ev:any) => any, options?: boolean | AddEventListenerOptions | undefined)=>void {
        return this.$el.addEventListener.bind(this.$el)
    }

    get removeEventListener(){
        return this.$el.removeEventListener.bind(this.$el)
    }
}